/* Raj Akula
   Project 1
   CSC 402
   
   g++ Compiler
   Mac OSX 
   Intel Core 2 Duo
   4G RAM
 
 */

# include <string>
# include <iostream>
using namespace std;


//Enum Declarations
enum HandValue 
{	 
	HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, 
    STRAIGHT, FLUSH, FULL_HOUSE,
    FOUR_OF_A_KIND, STRAIGHT_FLUSH 
};

enum CardSuit 
{ 
	CLUBS, DIAMONDS, HEARTS, SPADES 
};

enum CardRank 
{ 
	ACE = 1, DEUCE, THREE, FOUR, FIVE, SIX, 
    SEVEN, EIGHT, NINE, TEN, JACK,
    QUEEN, KING 
};

//Structure Declarations 
struct Card
{
	int suit;
	int rank;	// The ranks in a Card are assumed to be in the range
};// ACE, ..., KING.


//Function Declarations
typedef Card Deck[52];
void initDeck(Deck);
void deal5(Deck);
void printHand(Deck);
void printSummary(int, int[]);
char findRank(int);
char findSuit(int);
string findHand(int);
int whichHand(Deck);
int countRank(Card[], int[]);
int whichNonRepeatedHandType(Card[]);
int whichRepeatedHandType(int[]);
bool isFlush(Card[]);
bool isStraight(Card[]);



//Main Function...................................................................
int main()
{
	//Seeding randum number!
	int seed;
	cout << "Enter a seed integer for the random number generator: ";
	cin >> seed;
	srand(seed);
	
	//Number of hands to Deal!
	int hands;
	cout << "Enter number of hands to deal: ";
	cin >> hands;
	
	//Initilize hands
	Deck deck;
	initDeck(deck);
	
	cout << "\n";
	int handsDealt[9]; //array for all the different hand types
	
	for(int i = 0; i < 9; i++)
	{
		handsDealt[i] = 0;
	}
	
	int hand;
	
	//Deal cards and evaluate
	for(int i = 0; i < hands; i++)
	{
		deal5(deck);
		
		//Print 1'st 10 hands
		if(i < 10)
		{
			cout << "Hand #" << i+1 << ":\n";
			printHand(deck);
			hand = whichHand(deck);
			cout << findHand(hand) << "\n\n";
		}
		//Print 1'st of each type of hand after the first 10 hands... 
		else
		{
			hand = whichHand(deck);
			if(handsDealt[hand] == 0)
			{
				cout << "Hand #" << i+1 << ":\n";
				printHand(deck);
				cout << findHand(hand) << "\n\n";
			}
		}
		handsDealt[hand] = handsDealt[hand] + 1;
	}
	
	//Print Summary Results
	printSummary(hands, handsDealt);

	return 0;
}


//Function Definitions.....................................................

//Initlize Deck
void initDeck(Deck deck)
{
	int index = 0;
	for(int suit = CLUBS; suit <= SPADES; suit++)
	{
		for(int rank = ACE; rank <= KING; rank++)
		{
			deck[index].suit = (CardSuit) suit;
			deck[index++].rank = (CardRank) rank;
		}
	}
	cout << index;
}

// Randomly select five cards to place at the end of the deck
// i.e. in positions 47...51.
void deal5(Deck deck)
{
	for (int i = 51; i >= 47; i--)
	{
		int randIndex = rand() % (i+1);
		Card tmp = deck[randIndex];
		deck[randIndex] = deck[i];
		deck[i] = tmp;
	}
}

//Prints hand
void printHand(Deck deck)
{
	for(int i = 47; i < 52; i++)
	{
		cout << findRank(deck[i].rank) << findSuit(deck[i].suit) << " ";
	}
	cout << "\n";
}

//Prints Summary
void printSummary(int hands, int handDealt[])
{
	cout << "\n\nNo of hands: " << hands;
	cout << "\nFrequencies of rank categories --\n";
	for(int i = 0; i < 9; i++)
	{
		cout << findHand(i) << " : " << double(handDealt[i])/double(hands)*100 << "%\n";
	}
}

//Turns an rank thats in Integer format to Character format
char findRank(int rankInteger)
{
	char rankCharacter;
	switch(rankInteger)
	{
		case ACE:
			rankCharacter = 'A';
			break;
		case DEUCE:
			rankCharacter = '2';
			break;
		case THREE:
			rankCharacter = '3';
			break;
		case FOUR:
			rankCharacter = '4';
			break;
		case FIVE:
			rankCharacter = '5';
			break;
		case SIX:
			rankCharacter = '6';
			break;
		case SEVEN:
			rankCharacter = '7';
			break;
		case EIGHT:
			rankCharacter = '8';
			break;
		case NINE:
			rankCharacter = '9';
			break;
		case TEN:
			rankCharacter = 'T';
			break;
		case JACK:
			rankCharacter = 'J';
			break;
		case QUEEN:
			rankCharacter = 'Q';
			break;
		case KING:
			rankCharacter = 'K';
			break;
	}	
	return rankCharacter;
}

//Turns a suit in Integer format into Character format
char findSuit(int suitInteger)
{
	char suitCharacter;
	switch(suitInteger)
	{
		case CLUBS:
			suitCharacter = 'C';
			break;
		case DIAMONDS:
			suitCharacter = 'D';
			break;
		case HEARTS:
			suitCharacter = 'H';
			break;
		case SPADES:
			suitCharacter = 'S';
			break;
	}
	return suitCharacter;
}

//Turns a hand in Integer format into String format
string findHand(int handInteger)
{
	string handString;
	switch(handInteger)
	{
		case HIGH_CARD:
			handString = "High Card";
			break;
		case PAIR:
			handString = "Pair";		
			break;
		case TWO_PAIR:
			handString = "Two Pair";		
			break;
		case THREE_OF_A_KIND:
			handString = "Three of a Kind";		
			break;
		case STRAIGHT:
			handString = "Straight";		
			break;
		case FLUSH:
			handString = "Flush";		
			break;
		case FULL_HOUSE:
			handString = "Full House";		
			break;
		case FOUR_OF_A_KIND:
			handString = "Four of a Kind";		
			break;
		case STRAIGHT_FLUSH:
			handString = "Straight Flush";		
			break;
	}
	return handString;
}

//Main Function that tells you which hand was generated
int whichHand(Deck deck)
{
	int counter = 0;
	Card hand[5];
	
	for(int i = 47; i < 52; i++)
	{
		hand[counter] = deck[i];
		counter++;
	}
	
	int noRanks[KING+1];
	
	for (int i = 0; i <= KING; i++) {
		noRanks[i] = 0;
	}
	
	int highestRepeat = countRank(hand, noRanks);
	
	int handType = -1;

	if(highestRepeat > 1)
	{
		handType = whichRepeatedHandType(noRanks);
	}
	else 
	{
		handType = whichNonRepeatedHandType(hand);
	}
	
	return handType;

}

//Used to see if it is a Repeat Card's hand or a nonRepeat hand.
//If there is a repeat card hand then it also helps with evaluating which repeat hand!
int countRank(Card hand[], int noRanks[])
{
	int highestRepeat = 0;
	for(int i = 0; i < 5; i++)
	{
		noRanks[hand[i].rank] = noRanks[hand[i].rank] + 1; 
	}
	
	for(int i = 0; i <= KING; i++)
	{
		if(highestRepeat < noRanks[i])
		{
			highestRepeat = noRanks[i];
		}
	}
	
	return highestRepeat;
}

//Used to see which repeated hand has been generated!
int whichRepeatedHandType(int noRanks[])
{
	bool fourKind = false;
	bool threeKind = false;
	bool twoPair = false;
	bool pair = false;
	
	for (int i = 0; i <= KING; i++)
	{
		if(noRanks[i] == 4)
		{
			fourKind = true;
		}
		else if(noRanks[i] == 3)
		{
			threeKind = true;
		}
		else if((noRanks[i] == 2) && (pair == true))
		{
			twoPair = true;
		}
		else if(noRanks[i] == 2)
		{
			pair = true;
		}
		
	}
	
	if(fourKind)
	{
		return FOUR_OF_A_KIND;
	}
	else if(threeKind && pair)
	{
		return FULL_HOUSE;
	}
	else if(threeKind)
	{
		return THREE_OF_A_KIND;
	}
	else if(twoPair)
	{	
		return TWO_PAIR;
	}
	else if(pair)
	{
		return PAIR;
	}
	else {
		cout << "Something went REALLY REALLY WRONG!!!";
		exit(-1);
	}
		
}

//Used to see which nonRepeated hand has been generated!
int whichNonRepeatedHandType(Card hands[])
{
	bool flush = isFlush(hands);
	bool straight= isStraight(hands);
	
	if((flush == true) && (straight == true))
	{
		return STRAIGHT_FLUSH;
	}
	else if(flush == true)
	{
		return FLUSH;
	}
	else if(straight == true)
	{
		return STRAIGHT;
	}
	else 
	{
		return HIGH_CARD;
	}
}

//Checks for flush see's if all the suits are same in hand!
bool isFlush(Card hands[])
{
	int suit = hands[0].suit;
	if( (suit == hands[1].suit) && (suit == hands[2].suit) && (suit == hands[3].suit) && (suit == hands[4].suit))
		return true;
	else 
		return false;

}

//Checks for straight if the highest and lowest number are different by 5.
bool isStraight(Card hands[])
{
	bool ace = false;
	int max = 1;
	int min = 13;
	
	for(int i = 0; i < 5; i++)
	{
		if(max < hands[i].rank)
		{
			max = hands[i].rank;
		}
		if(min > hands[i].rank)
		{
			min = hands[i].rank;
		}
		
	}
	if((max-min) == 4){
		return true;
	}

	for(int i = 0; i < 5; i++)
	{
		if(hands[i].rank == ACE)
		{
			ace = true;
			hands[i].rank = 14;
			i = 0;
			min = 14;
			
		}
		if(max < hands[i].rank)
		{
			max = hands[i].rank;
		}
		if(min > hands[i].rank)
		{
			min = hands[i].rank;
		}
		
	}
	
	if((max-min) == 4){
		return true;
	}
	else{
		return false;
	}
	
}